#pragma once

#include <cstdint>
#include <cstring>
#include <string>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <atomic>
#include <glog/logging.h>

#include "rwlock.h"
#include "tlbtree/include/tlbtree.h"

using std::string;

namespace nsbtree {

#ifndef KEY_TYPE
    using _key_t = uint64_t;
#else 
    using _key_t  = KEY_TYPE;
#endif

enum ReqType {PUT, UPDATE, DELETE};
// Request meta data: 8 Bytes
struct Request {
    uint16_t op;
    uint16_t crc;
    uint32_t val_size;
    char keyvalue[0];
    // key size: 8 Bytes
    // val size: variable
    inline uint32_t Length() {
        return sizeof(Request) + 8 + val_size;
    }
};
constexpr uint16_t SIMPLE_CRC = (uint16_t)0x5AB3;
constexpr uint32_t NSBTREE_PAGE_SIZE = 16 * 1024; // or 8 KiB for small key value items
constexpr uint32_t INVALID_PAGE_ID = UINT32_MAX;

const _key_t MAX_KEY = std::numeric_limits<_key_t>::max();
const _key_t MIN_KEY = typeid(_key_t) == typeid(double) || typeid(_key_t) == typeid(float) 
                            ? -1 * MAX_KEY : std::numeric_limits<_key_t>::min();

struct NSBOpt {
    string dbdir;
    string nvmdir;

    uint64_t dramsize;
    uint64_t nvmsize;
    uint64_t ssdsize;
};

inline size_t file_size(const char *fname) {
    struct stat buffer; stat(fname, &buffer);
    return buffer.st_size;
}

inline bool file_exist(const char *fname) {
    struct stat buffer;
    return (stat(fname, &buffer) == 0);
}

inline bool folder_exist(const char *fname) {
    struct stat buffer;
    return stat(fname, &buffer) == 0 && S_ISDIR(buffer.st_mode);
}

// LeafPages are stored on disk while accessed through Buffer Manager
class LeafPage {
public:
    struct Record {
        _key_t key;
        uint32_t val_off;
        uint32_t val_size;
    };
    static constexpr int CAPACITY = 62;
    static constexpr int MAX_OFF = NSBTREE_PAGE_SIZE - sizeof(Record) * CAPACITY - 24;
    uint32_t cur_off_;
    uint32_t count_;
    uint32_t pageid_;
    uint32_t next_pageid_;
    _key_t splitkey;
    Record recs_[CAPACITY];
    char buff_[0];
    
public:
    _key_t Insert(const _key_t key, const string & val, LeafPage * new_leaf = nullptr);

    bool Lookup(const _key_t key, string & val);

    bool Update(const _key_t key, const string & val);

    inline bool Delete(const _key_t key) {
        return false;
    }

    inline bool ShouldSplit(std::string val) {
        return count_ >= CAPACITY || cur_off_ + val.size() > MAX_OFF;
    }

    int Scan(const _key_t startKey, int len, vector<std::string> & out);

    void Clear() {count_ = 0; cur_off_ = 0;}

    void Compact();
};

// NSBtree: a KVStore that puts inner nodes in NVM and Leaf nodes in SSD
class NSBtree {
private:
    tlbtree_up::TLBtree * uptree_;
    RWLock * locktable_;
    int log_fd_;
    int db_fd_;
    std::atomic<uint32_t> cur_page_id_;
    uint64_t log_offset_;
    bool sync_;

    void Append(Request * req, bool sync);

    uint32_t NewPage() {return cur_page_id_.fetch_add(1); }

    void ReadPage(uint32_t page_id, char * buff);

    void WritePage(uint32_t page_id, char * buff);

public:
    NSBtree(NSBOpt & opt);

    ~NSBtree();

    void Put(const _key_t & key, const std::string & val, bool sync = false);

    bool Get(const _key_t & key, std::string & val);

    bool Update(const _key_t & key, std::string & val, bool sync = false);

    bool Delete(const _key_t & key, bool sync = false);

    int Scan(const _key_t startKey, int len, vector<std::string> & out);
};

} // namespace btree
